#include <iostream>
#include <stdexcept>
#include <time.h>
#include "Date.h"
#include "StringUtils.h"

const unsigned int Date::numberDays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
const char* const Date::strMonth[::MAX_MONTH] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

namespace
{
	const int SUCCESS = 0;

	const int MONTH_START =1;
	const int YEAR_START = 1900;	
	const int SIXTYTH_SECOND = 1;
	const int SIXTYTH_MINUTE = 1;
	const int TWENTY_FOURTH_HOUR = 1;
	const int THENTY_NINTH_DAY = 1;

	const int MULTIPLE_OF_4 = 4;
	const int MULTIPLE_OF_100 = 100;
	const int MULTIPLE_OF_400 = 400;
}


Date::Date()
{
	time_t currentTime = time( NULL );
	struct tm timeInfo;
	errno_t errNumber =  localtime_s (&timeInfo, &currentTime );
	if( ::SUCCESS != errNumber ) {
		throw std::logic_error("Error format time");
	}
	second = static_cast<unsigned int>(timeInfo.tm_sec);
	minute = static_cast <unsigned int> (timeInfo.tm_min);
	hour = static_cast <unsigned int> (timeInfo.tm_hour);
	day = static_cast <unsigned int> (timeInfo.tm_mday);
	month = static_cast <Months> (timeInfo.tm_mon + ::MONTH_START);
	year = static_cast <unsigned int> (timeInfo.tm_year + ::YEAR_START );
}

Date::Date(const unsigned int year, const Months month, const unsigned int day)
{
	Date(year, month, day, ::NULL_VALUE, ::NULL_VALUE, ::NULL_VALUE);

}

Date::Date(const unsigned int hour, const unsigned int minute, const unsigned int second)
{
	time_t currentTime = time( NULL );
	struct tm timeInfo;
	errno_t errNumber =  localtime_s (&timeInfo, &currentTime );
	if( ::SUCCESS != errNumber )
	{
		throw std::logic_error("Error format time");
	}
	day = static_cast <unsigned int> (timeInfo.tm_mday);
	month = static_cast <Months> (timeInfo.tm_mon + ::MONTH_START);
	year = static_cast <unsigned int> (timeInfo.tm_year + ::YEAR_START );

	this->hour = hour;
	this->minute = minute;
	this->second = second;
}

Date::Date(const unsigned int year, 
	const Months month, 
	const unsigned int day, 
	const unsigned int hour,
	unsigned int minute, 
	const unsigned int second)
	: 
year(::NULL_VALUE),
	month((Months)::NULL_VALUE),
	day(::NULL_VALUE),
	hour(::NULL_VALUE),
	minute(::NULL_VALUE),
	second(::NULL_VALUE)
{

	buildSecons(second);
	buildMinutes(minute);
	buildHour(hour);

	buildMonth(month);
	buildYear(year);
	int monthToAdd = buildDay(day, this->month, this->year);
	if( monthToAdd > 0 ) {
		buildMonth((Months)monthToAdd);
		buildYear(0);
	}
}

void Date::buildYear( const unsigned int year ) 
{
	this->year += year;

	if( ::MAX_YEAR < this->year || ::MIN_YEAR > this->year )
	{
		throw std::logic_error("Incorrect date");
	} 
}

void Date::buildMonth( const Months month ) 
{
	this->month = (Months) ((int) this->month + month) ;
	if( ::MAX_MONTH < this->month )
	{
		this->month = (Months)( (int) this->month % ::MAX_MONTH );
		this->year += (month / ::MAX_MONTH);
	}
}

int Date::buildDay( const unsigned int day, const Months month, const unsigned int year ) 
{
	this->day += day;
	unsigned int daysInMonth = getDaysInMonth ( month, Date::isLeapYear (year) );
	if( daysInMonth < this->day)
	{
		this->day = this->day % daysInMonth;
		return day / daysInMonth;
		

		//this->month = (Months)( this->month + day / daysInMonth);
	} else {
		return 0;
	}
}

void Date::buildHour( const unsigned int hour ) 
{
	this->hour += hour;
	if( ::MAX_HOUR < this->hour )
	{
		this->hour = this->hour % (::MAX_HOUR + ::TWENTY_FOURTH_HOUR);
		this->day += (hour / ::MAX_HOUR);
	}
}

void Date::buildMinutes( unsigned int minute ) 
{
	this->minute += minute;
	if( ::MAX_MINUTE < this->minute )
	{
		this->minute = this->minute % (::MAX_MINUTE + ::SIXTYTH_MINUTE);
		this->hour += (minute / ::MAX_MINUTE );
	}
}

void Date::buildSecons( const unsigned int second ) 
{
	this->second += second;
	if( ::MAX_SECOND < this->second )
	{
		this->second = this->second % (::MAX_SECOND + ::SIXTYTH_SECOND);
		this->minute += ( second / ::MAX_SECOND );
	}
}


Date Date::addYear(const int year) const
{
	Date date( year + this->year, month, day, hour, minute, second);
	return date;
}

Date Date::addMonth(const int month) const
{
	Date date( year, (Months)(month + (int) this->month), day, hour, minute, second);
	return date;
}

Date Date::addDay(const int day) const
{
	Date date( year, month, day + this->day, hour, minute, second);
	return date;
}

Date Date::addHour(const int hour) const{
	Date date( year, month, day, hour + this->hour, minute, second);
	return date;
}

Date Date::addMinute(const int minute) const
{
	Date date( year, month, day, hour, minute + this->minute, second);
	return date;
}

Date Date::addSecond(const int second) const
{
	Date date( year, month, day, hour, minute, second + this->second);
	return date;
}

unsigned int Date::getYear() const
{
	return year;
}

Date::Months Date::getMonth() const
{
	return month;
}

unsigned int Date::getDay() const
{
	return day;
}

unsigned int Date::getHour() const
{
	return hour;
}

unsigned int Date::getMinute() const
{
	return minute;
}

unsigned int Date::getSecond() const
{
	return second;
}


std::string Date::toString() const 
{

	char buffer[SIZE_BUFFER] = {0};
	_snprintf_s(buffer, ::SIZE_BUFFER, "%04d-%s-%02d %02d:%02d:%02d",
		year, strMonth[static_cast<int>(month)-1], day, hour, minute, second);
	std::string dateString(buffer);
	return dateString;
}

bool Date::isLeapYear (const unsigned int year) 
{
	if( (::SUCCESS == year % ::MULTIPLE_OF_100) ||
		((::SUCCESS != year % ::MULTIPLE_OF_100) &&
		(::SUCCESS == year % ::MULTIPLE_OF_4)) ) 
	{
		return true;
	} else {
		return false;
	}
}

unsigned int Date::getDaysInMonth (Months month, bool isYearLeap)
{
	if( isYearLeap && Date::Feb == month )
	{
		return numberDays[month - 1] + ::THENTY_NINTH_DAY;
	} else {
		return numberDays[month - 1];
	}
}

DateInterval Date::getInterval(const Date& another) const
{
	return DateInterval(another.year - year, 
		another.month - month, 
		another.day - day, 
		another.hour - hour,
		another.minute - minute, 
		another.second - second
		);
}

Date Date::addInterval(const DateInterval& interval) const
{
	return Date(year + interval.getYearDifference(), 
		(Months) ((int) month + interval.getMonthDifference()),
		day + interval.getDayDifference(),
		hour + interval.getHourDifference(),
		minute + interval.getMinuteDifference(),
		second + interval.getSecondDifference());
}

std::string Date::formatDate(const std::string format) const
{
	char strYear[SIZE_YEAR] = {::NULL_VALUE};
	char strDay[SIZE_DAY] = {::NULL_VALUE};
	char strHour[SIZE_HOUR] = {::NULL_VALUE};
	char strMinute[SIZE_MINUTE] = {::NULL_VALUE};
	char strSecond[SIZE_SECOND] = {::NULL_VALUE};

	_snprintf_s(strYear, SIZE_YEAR, "%04d", year);
	_snprintf_s(strDay, SIZE_DAY, "%02d", day);
	_snprintf_s(strHour, SIZE_HOUR, "%02d", hour);
	_snprintf_s(strMinute, SIZE_MINUTE, "%02d", minute);
	_snprintf_s(strSecond, SIZE_SECOND, "%02d", second);

	std::string date(format);

	StringUtils::replaceAll(date, ::YEAR_FORMAT, strYear);
	StringUtils::replaceAll(date, ::MONTH_FORMAT, Date::strMonth[static_cast<int>(month) - 1]);
	StringUtils::replaceAll(date, ::DAY_FORMAT, strDay);
	StringUtils::replaceAll(date, ::HOUR_FORMAT, strHour);
	StringUtils::replaceAll(date, ::MINUTE_FORMAT, strMinute);
	StringUtils::replaceAll(date, ::SECOND_FORMAT, strSecond);


	return date;
}